/*  Copyright © 2009 Blake Hegerle -*- C++ -*- */

/*  This file is part of metal.
 
    Metal is free software: you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    General Public License for more details.
 
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see
    <http://www.gnu.org/licenses/>.

*/

#pragma once

#include <type_traits>

#include "param.h"
#include "type.h"
#include "binding.h"
#include "function.h"
#include "expr.h"
#include "type_promotion.h"
#include "../util.h"
#include "module.h"

namespace metal {
  namespace meta {
    template<typename Scope, typename MetaExpr, typename... Args>
    struct definition_type_check;

    template<typename T, typename Scope = bindings<> >
    struct type_check {
      static const bool is_bound = false;
      static const bool is_safe = false;
      typedef void type;
    };

    template<typename I, typename Scope>
    struct type_check<ident_expr<I>, Scope> {
      typedef typename get<I, Scope>::type type;
      static const bool is_bound = !std::is_void<type>::value;
      static const bool is_safe = true;
    };

    template<typename P, typename Scope>
    struct type_check<constant_expr<P>, Scope> {
      static const bool is_bound = true;
      static const bool is_safe = true;
      typedef typename std::remove_cv<P>::type type;
    };

    template<typename Scope, typename Sig, typename... Args>
    struct arg_type_check {
      static const bool is_bound = false, is_safe = false;
    };

    template<typename Scope, typename RT>
    struct arg_type_check<Scope, list<RT> > {
      static const bool is_bound = true, is_safe = true;
      typedef RT return_type;
    };

    template<typename Scope, typename RT, typename P, typename... Params, typename E, typename... Args>
    struct arg_type_check<Scope, list<RT, P, Params...>, E, Args...> {
      typedef type_check<E, Scope> check;
      typedef arg_type_check<Scope, list<RT, Params...>, Args...> next;

      static const bool is_bound = check::is_bound && next::is_bound;
      static const bool is_safe =
	allow_conversion<typename check::type, P>::value
	&& check::is_safe
	&& next::is_safe;

      typedef RT return_type;
    };

    template<typename Expr, typename... Args, typename Scope>
    struct type_check<call_expr<Expr, Args...>, Scope> {
      typedef typename type_check<Expr, Scope>::type sig;

      static const bool correct_arg_count =
	count<Args...>::value == length<sig>::value - 1;

      typedef arg_type_check<Scope, sig, Args...> ac;

      static const bool is_bound = !std::is_void<sig>::value
	&& ac::is_bound;
      typedef typename head<sig>::type type;
      static const bool is_safe = ac::is_safe;
    };

    template<typename E, typename Scope>
    struct type_check<unary_expr<arithmatic_tag, E>, Scope> {
      static const bool is_bound = type_check<E, Scope>::is_bound;
      typedef typename type_check<E, Scope>::type type;

      static const bool is_safe = std::is_arithmetic<type>::value;
    };

    template<typename E, typename Scope>
    struct type_check<unary_expr<integral_tag, E>, Scope> {
      static const bool is_bound = type_check<E, Scope>::is_bound;
      typedef typename type_check<E, Scope>::type type;

      static const bool is_safe = std::is_integral<type>::value;
    };

    template<typename E, typename Scope>
    struct type_check<unary_expr<boolean_tag, E>, Scope> {
      static const bool is_bound = type_check<E, Scope>::is_bound;
      typedef typename type_check<E, Scope>::type type;

      static const bool is_safe = std::is_same<type, bool>::value;
    };

    template<typename Tag, typename Type>
    struct is_type_safe_for_tag;

    template<typename Type>
    struct is_type_safe_for_tag<arithmatic_tag, Type> {
      static const bool value = std::is_arithmetic<Type>::value;
    };

    template<typename Type>
    struct is_type_safe_for_tag<integral_tag, Type> {
      static const bool value = std::is_integral<Type>::value;
    };

    template<typename Tag, typename Left, typename Right, typename Scope>
    struct type_check<binary_expr<Tag, Left, Right>, Scope> {
      typedef type_check<Left, Scope> l;
      typedef type_check<Right, Scope> r;

      typedef typename l::type l_type;
      typedef typename r::type r_type;

      static const bool is_bound = l::is_bound && r::is_bound;
      typedef typename promote<l_type, r_type>::type type;

      static const bool is_safe = !std::is_same<type, void>::value
	&& is_type_safe_for_tag<Tag, type>::value;
    };

    template<typename Left, typename Right, typename B>
    struct type_check<binary_expr<boolean_tag, Left, Right>, B> {
      typedef type_check<Left, B> l;
      typedef type_check<Right, B> r;

      typedef typename l::type l_type;
      typedef typename r::type r_type;

      static const bool is_bound = l::is_bound && r::is_bound;
      typedef bool type;
      static const bool is_safe = std::is_same<l_type, bool>::value
	&& std::is_same<l_type, bool>::value;
    };

    template<typename Left, typename Right, typename B>
    struct type_check<binary_expr<comparison_tag, Left, Right>, B> {
      typedef type_check<Left, B> l;
      typedef type_check<Right, B> r;

      typedef typename l::type l_type;
      typedef typename r::type r_type;

      static const bool is_bound = l::is_bound && r::is_bound;
      typedef bool type;
      static const bool is_safe = !std::is_same<type, void>::value;
    };

    template<typename Expr, typename B>
    struct type_check<eval_expr<Expr>, B> {
      typedef type_check<Expr, B> check;

      static const bool is_safe = check::is_safe;
      static const bool is_bound = check::is_bound;
      typedef typename check::type type;
    };

    template<typename Expr, typename BoundIdent, typename BoundExpr, typename... Bindings, typename Scope>
    struct type_check<eval_expr<Expr, binding<BoundIdent, BoundExpr>, Bindings...>, Scope> {
      typedef type_check<BoundExpr, Scope> check;
      typedef typename bind<
	BoundIdent,
	typename check::type,
	Scope>::type next_scope;
      typedef eval_expr<Expr, Bindings...> next_expr;
      typedef type_check<next_expr, next_scope> next;

      static const bool is_safe = check::is_safe && next::is_safe;
      static const bool is_bound = check::is_bound && next::is_bound;
      typedef typename next::type type;
    };

    template<typename T, typename S, typename... Elems>
    struct array_expr_check;

    template<typename T, typename S>
    struct array_expr_check<T, S> {
      static const bool is_safe = true, is_bound = true;
    };

    template<typename T, typename S, typename E, typename... Elems>
    struct array_expr_check<T, S, E, Elems...> {
      typedef array_expr_check<T, S, Elems...> next;
      typedef type_check<E, S> check;

      static const bool is_safe =
	allow_conversion<typename check::type, T>::value
	&& next::is_safe
	&& check::is_safe;
      static const bool is_bound = next::is_bound && check::is_bound;
    };

    template<typename FirstElem, typename... Elems, typename Scope>
    struct type_check<array_expr<FirstElem, Elems...>, Scope> {
      typedef type_check<FirstElem, Scope> check;
      typedef typename check::type e_type;
      typedef array_expr_check<e_type, Scope, Elems...> next;

      typedef e_type *type;
      static const bool is_safe = check::is_safe && next::is_safe;
      static const bool is_bound = check::is_bound && next::is_bound;
    };

    template<typename Applies, typename Scope>
    struct map_bindings;

    template<typename I, typename Expr, typename Scope>
    struct map_bindings<bindings<binding<I, Expr> >, Scope> {
      typedef type_check<Expr, Scope> check;

      typedef typename element_type<typename check::type>::type e_type;
      typedef typename bind<I, e_type, Scope>::type inner_scope;

      static const bool is_safe = check::is_safe;
      static const bool is_bound = check::is_bound;
    };

    template<typename I, typename Expr, typename... Bindings, typename Scope>
    struct map_bindings<bindings<binding<I, Expr>, Bindings...>, Scope> {
      typedef type_check<Expr, Scope> check;
      typedef typename element_type<typename check::type>::type e_type;

      typedef map_bindings<bindings<Bindings...>, Scope> next;
      typedef typename bind<I, e_type, typename next::inner_scope>::type inner_scope;

      static const bool is_safe = check::is_safe && next::is_safe;
      static const bool is_bound = check::is_bound && next::is_bound;
    };

    template<typename Expr, typename... Bindings, typename Scope>
    struct type_check<map_expr<Expr, Bindings...>, Scope> {
      typedef map_bindings<bindings<Bindings...>, Scope> application;

      typedef type_check<Expr, typename application::inner_scope> check;

      static const bool is_safe = application::is_safe && check::is_safe;
      static const bool is_bound = application::is_bound && check::is_bound;
      typedef typename check::type *type;
    };

    template<typename IfExpr, typename ThenExpr, typename ElseExpr, typename Scope>
    struct type_check<cond_expr<IfExpr, ThenExpr, ElseExpr>, Scope> {
      typedef type_check<IfExpr, Scope> if_check;
      typedef type_check<ThenExpr, Scope> then_check;
      typedef type_check<ElseExpr, Scope> else_check;

      static const bool is_safe = if_check::is_safe
	&& then_check::is_safe
	&& else_check::is_safe
	&& std::is_same<bool, typename if_check::type>::value
	&& std::is_same<typename then_check::type, typename else_check::type>::value;
      static const bool is_bound = if_check::is_bound
	&& then_check::is_bound
	&& else_check::is_bound;
      typedef typename then_check::type type;
    };

    template<typename I, typename Range, typename Filter, typename Expr, typename Scope>
    struct type_check<filter_expr<Expr, I, Range, Filter>, Scope> {
      typedef type_check<Range, Scope> r_check;
      typedef typename element_type<typename r_check::type>::type r_type;
      typedef typename bind<I, r_type, Scope>::type new_scope;
      typedef type_check<Filter, new_scope> f_check;
      typedef type_check<Expr, new_scope> e_check;

      static const bool is_safe = r_check::is_safe
	&& f_check::is_safe
	&& e_check::is_safe
	&& std::is_same<bool, typename f_check::type>::value;
      static const bool is_bound = r_check::is_bound
	&& e_check::is_bound
	&& f_check::is_bound;
      typedef typename e_check::type *type;
    };

    template<typename A, typename I, typename Scope>
    struct type_check<subscript_expr<A, I>, Scope> {
      typedef type_check<A, Scope> a_check;
      typedef type_check<I, Scope> i_check;

      static const bool is_safe = a_check::is_safe
	&& i_check::is_safe
	&& std::is_integral<typename i_check::type>::value;
      static const bool is_bound = a_check::is_bound && i_check::is_bound;
      typedef typename element_type<typename a_check::type>::type type;
    };

    template<typename T, typename M>
    struct member_type {
      typedef void type;
    };

    template<typename... Members, typename M>
    struct member_type<structure<Members...>, M> {
      typedef typename get<M, bindings<Members...>>::type type;
    };

    template<typename Expr, typename M, typename Scope>
    struct type_check<dot_expr<Expr, M>, Scope> {
      typedef type_check<Expr, Scope> check;
      typedef typename member_type<typename check::type, M>::type type;

      static const bool is_safe = check::is_safe;
      static const bool is_bound = check::is_bound
	&& !std::is_same<void, type>::value;
    };

    template<
      typename... Members, 
      typename Scope
    >
    struct type_check<
      cons_expr<
	structure<Members...>
      >, 
      Scope> {
      typedef structure<Members...> type;
      static const bool is_bound = true;
      static const bool is_safe = true;
    };

    template<
      typename... Members, 
      typename I,
      typename Expr,
      typename... Bindings,
      typename Scope
      >
    struct type_check<
      cons_expr<
	structure<Members...>,
	binding<I, Expr>,
	Bindings...
	>, Scope
      > {
      typedef cons_expr<structure<Members...>, Bindings...> next;
      typedef type_check<next, Scope> next_check;

      typedef typename get<I, bindings<Members...>  >::type m_type;
      typedef type_check<Expr, Scope> v_check;

      typedef structure<Members...> type;
      static const bool is_bound =
	v_check::is_bound
	&& next_check::is_bound;
      static const bool is_safe =
	v_check::is_safe
	&& !std::is_same<typename v_check::type, void>::value
	&& allow_conversion<typename v_check::type, m_type>::value
	&& next_check::is_safe;
    };

    template<typename Type, typename Expr, typename Scope>
    struct type_check<cast_expr<Type, Expr>, Scope> {
      typedef type_check<Expr, Scope> check;

      static const bool is_bound = check::is_bound;
      static const bool is_safe = 
	std::is_arithmetic<Type>::value
	&& std::is_arithmetic<typename check::type>::value;

      typedef Type type;
    };

    template<typename Type, typename Expr, typename... Params, typename Scope>
    struct type_check<lambda_expr<Type, Expr, Params...>, Scope > {
      typedef definition_type_check<Scope, Expr, Params...> check;

      typedef typename decl_signature<Type, Params...>::type type;
      static const bool is_bound = check::is_bound;
      static const bool is_safe = check::is_safe
	&& allow_conversion<typename check::type, Type>::value;
    };
  }
}
